/**
 * 通用函数
 */

import * as THREE from "./three.js/build/three.module.js";
import { AmmoLib } from "./A3Class.js";
import settings from "./A3Settins.js";

// activationState

const ACTIVE_TAG = 1;
const ISLAND_SLEEPING = 2;
const WANTS_DEACTIVATION = 3;
const DISABLE_DEACTIVATION = 4;
const DISABLE_SIMULATION  = 5;

const CollisionFlags = {
  CF_STATIC_OBJECT : 1,
  CF_KINEMATIC_OBJECT : 2,
  CF_NO_CONTACT_RESPONSE : 4,
  CF_CUSTOM_MATERIAL_CALLBACK : 8,//this allows per-triangle material (friction/restitution)
  CF_CHARACTER_OBJECT : 16,
  CF_DISABLE_VISUALIZE_OBJECT : 32, //disable debug drawing
  CF_DISABLE_SPU_COLLISION_PROCESSING : 64,//disable parallel/SPU processing
  CF_HAS_CONTACT_STIFFNESS_DAMPING : 128,
  CF_HAS_CUSTOM_DEBUG_RENDERING_COLOR : 256,
  CF_HAS_FRICTION_ANCHOR : 512,
  CF_HAS_COLLISION_SOUND_TRIGGER : 1024
}

const SPI = Math.PI;
const HPI = SPI * 0.5;
const DPI = SPI * 2;

const AxisX = new THREE.Vector3( 1, 0, 0 );
const AxisY = new THREE.Vector3( 0, 1, 0 );
const AxisZ = new THREE.Vector3( 0, 0, 1 );

// 添加坐标轴
function addAxesHelper ( mesh, size=1 ) {
  
  if( isNull( mesh ) ) { return null; }

  const axes = new THREE.AxesHelper( size );
  mesh.add(axes);

  return axes;

}

// 生成地形形状
// 
function createTerrainShape( 
  terrainWidthExtents, terrainDepthExtents, 
  terrainWidth, terrainDepth, 
  terrainMinHeight, terrainMaxHeight,
  heightData 
) {

  if( !AmmoLib ){

    console.error( "Ammo未初始化.");
    return null;

  }

  const margin = settings.margin.get();

  // This parameter is not really used, since we are using PHY_FLOAT height data type and hence it is ignored
  const heightScale = 1;

  // Up axis = 0 for X, 1 for Y, 2 for Z. Normally 1 = Y is used.
  const upAxis = 1;

  // hdt, height data type. "PHY_FLOAT" is used. Possible values are "PHY_FLOAT", "PHY_UCHAR", "PHY_SHORT"
  const hdt = "PHY_FLOAT";

  // Set this to your needs (inverts the triangles)
  const flipQuadEdges = false;

  // Creates height data buffer in Ammo heap
  const ammoHeightData = AmmoLib._malloc( 4 * terrainWidth * terrainDepth );

  // Copy the javascript height data array to the Ammo one.
  let p = 0;
  let p2 = 0;

  for ( let j = 0; j < terrainDepth; j ++ ) {

    for ( let i = 0; i < terrainWidth; i ++ ) {

      // write 32-bit float data to memory
      AmmoLib.HEAPF32[ ammoHeightData + p2 >> 2 ] = heightData[ p ];

      p ++;

      // 4 bytes/float
      p2 += 4;

    }

  }

  // Creates the heightfield physics shape
  const heightFieldShape = new Ammo.btHeightfieldTerrainShape(
    terrainWidth,
    terrainDepth,
    ammoHeightData,
    heightScale,
    terrainMinHeight,
    terrainMaxHeight,
    upAxis,
    hdt,
    flipQuadEdges
  );

  // Set horizontal scale
  const scaleX = terrainWidthExtents / ( terrainWidth - 1 );
  const scaleZ = terrainDepthExtents / ( terrainDepth - 1 );
  heightFieldShape.setLocalScaling( new AmmoLib.btVector3( scaleX, 1, scaleZ ) );

  heightFieldShape.setMargin( margin );

  return heightFieldShape;

}

// 获取mesh的顶点和面
function getMeshData( mesh ) {

  const index = mesh.geometry.index !== null ? mesh.geometry.index : undefined;
  const attributes = mesh.geometry.attributes;
  const scale = mesh.scale;

  if (attributes.position === undefined) {

      console.error('getMeshData(): Position attribute required for conversion.');
      return;

  }

  const position = attributes.position;

  let vertices = [];
  let faces = [];

  for (let i = 0; i < position.count; i++) {

      vertices.push({
          x: scale.x * position.getX(i),
          y: scale.y * position.getY(i),
          z: scale.z * position.getZ(i)
      });

  }

  if (index !== undefined) {

      for (let i = 0; i < index.count; i += 3) {

          faces.push({
              a: index.getX(i),
              b: index.getX(i + 1),
              c: index.getX(i + 2)
          });

      }

  } else {

      for (let i = 0; i < position.count; i += 3) {

          faces.push({
              a: i,
              b: i + 1,
              c: i + 2
          });

      }
  }

  return {
      vertices,
      faces
  }
}

// 获取mesh对应的bullet shape
function getMeshShape( mesh, scale,  isTriangle=true) {

  if( !AmmoLib ){

    console.error( "Ammo未初始化.");
    return null;

  }

  if ( !mesh.isMesh || !mesh.geometry ) {
    console.error( "不是一个合法的Mesh", mesh );
    return null;
  }

  if ( isNull( scale ) ) {
    scale = new THREE.Vector3( 1, 1, 1 );
  }
  
  const geometry = mesh.geometry;

  const margin = settings.margin.get();

  const parameters = geometry.parameters;

  let shape = undefined;

  if (geometry.isBufferGeometry){

    const type = geometry.type;

    switch(type){

      case "BoxGeometry":

        var width = parameters.width !== undefined ? parameters.width / 2 : 0.5;
        var height = parameters.height !== undefined ? parameters.height / 2 : 0.5;
        var depth = parameters.depth !== undefined ? parameters.depth / 2 : 0.5;

        shape = new AmmoLib.btBoxShape( new AmmoLib.btVector3( width, height, depth ) );
        break;

      case "ConeGeometry":
  
        var radius = parameters.radius !== undefined ? parameters.radius : 1;
        var height = parameters.height !== undefined ? parameters.height : 1;

        shape = new AmmoLib.btConeShape( radius, height );
        break;

      case "CylinderGeometry":

        var height = parameters.height !== undefined ? parameters.height / 2 : 0.5;
        const radiusTop = parameters.radiusTop !== undefined ? parameters.radiusTop : 1;
        const radiusBottom = parameters.radiusBottom !== undefined ? parameters.radiusBottom : 1;

        shape = new AmmoLib.btCylinderShape( new AmmoLib.btVector3( radiusTop, height, radiusBottom ) );
        break;
      
      case "SphereGeometry":

        var radius = parameters.radius !== undefined ? parameters.radius : 1;

        shape = new AmmoLib.btSphereShape( radius );
        break;
      
      case "CapsuleGeometry":

        var radius = parameters.radius !== undefined ? parameters.radius : 1;
        var height = parameters.height !== undefined ? parameters.height : 1;

        shape = new AmmoLib.btCapsuleShape( radius, height );
        break;

      case "TorusGeometry":

        shape = createGImpactCollision( mesh, scale );
        // shape = createConvexHullPhysicsShape( geometry.attributes.position.array, scale );  

        break;

      case "PlaneGeometry":

        var width = parameters.width !== undefined ? parameters.width / 2 : .5;
        var height = parameters.height !== undefined ? parameters.height / 2 : .5;
        var depth = .002; // 默认厚度

        shape = new AmmoLib.btBoxShape( new AmmoLib.btVector3( width, depth, height ) );
        // shape = createGImpactCollision( mesh );

        break;

      default:
        if( isTriangle ){
          shape = createGImpactCollision( mesh, scale );
        }
        else {
          shape = createConvexHullPhysicsShape( geometry.attributes.position.array, scale );  
        }        
        break;
    }

  }
  else {

    console.error("不支持的几何形状:", geometry);

  }

  if( !isNull ( shape ) ) {
    shape.setMargin( margin );
  }

  return shape;

}

// 空形状
function createEmptyShape() {

  if( !AmmoLib ){

    console.error( "Ammo未初始化.");
    return null;

  }

  const shape = new AmmoLib.btEmptyShape();

  return shape;
}

// 创建复合形状
function createCompoundShape() {

  if( !AmmoLib ){

    console.error( "Ammo未初始化.");
    return null;

  }

  const shape = new AmmoLib.btCompoundShape();

  shape.addChild = ( childShape, position, quaternion ) => {
    shape.addChildShape( toTransform( position, quaternion ), childShape);
  }

  return shape;

}

// 生成凸包物理性状
function createConvexHullPhysicsShape( coords , scale ) {

  if( !AmmoLib ){

    console.error( "Ammo is not initialized.");
    return null;

  }

  if ( isNull( scale ) ) {
    scale = { x: 1, y: 1, z: 1 };
  }

  const shape = new AmmoLib.btConvexHullShape();
  var tempBtVec3_1 = new AmmoLib.btVector3( 0, 0, 0 );

  for ( let i = 0, il = coords.length; i < il; i += 3 ) {

    tempBtVec3_1.setValue( coords[ i ], coords[ i + 1 ], coords[ i + 2 ] );
    const lastOne = ( i >= ( il - 3 ) );
    shape.addPoint( tempBtVec3_1, lastOne );

  }

  shape.setLocalScaling(new AmmoLib.btVector3(scale.x, scale.y, scale.z));
  // shape.updateBound();
  // console.log(shape);

  return shape;

}

// 生成紧凑型碰撞
function createGImpactCollision( mesh, scale ) {

  let faces, vertices;
  let totvert = 0;

  if ( mesh.isMesh ) {
      let data = getMeshData(mesh);

      faces = data.faces;
      vertices = data.vertices;

      totvert = vertices.length;

  }
  else {
    console.error("cannot make mesh shape for non-Mesh object");
  }

  if (totvert == 0) {
    console.error("no vertices to define mesh shape with");
  }

  if ( isNull( scale ) ) {
    scale = { x: 1, y: 1, z: 1 };
  }

  /* vertices, faces */
  let ammoMesh = new AmmoLib.btTriangleMesh();

  for (let i = 0, l = faces.length; i < l; i++) {
      let a = faces[i].a;
      let b = faces[i].b;
      let c = faces[i].c;
      ammoMesh.addTriangle(
          new AmmoLib.btVector3(vertices[a].x, vertices[a].y, vertices[a].z),
          new AmmoLib.btVector3(vertices[b].x, vertices[b].y, vertices[b].z),
          new AmmoLib.btVector3(vertices[c].x, vertices[c].y, vertices[c].z),
          false
      );
  }

  let triangleShape = new AmmoLib.btGImpactMeshShape(ammoMesh);
  triangleShape.setLocalScaling(new AmmoLib.btVector3(scale.x, scale.y, scale.z));
  triangleShape.updateBound();

  // console.log(triangleShape)

  return triangleShape;

}

// 生成刚体物理模型
function createRigidBody( physicsShape, mass, position, quaternion, inertia ) {

  if( !AmmoLib ){

    console.error( "Ammo is not initialized.");
    return null;

  }

  const transform = new AmmoLib.btTransform();
  transform.setIdentity();
  transform.setOrigin( new AmmoLib.btVector3( position.x, position.y, position.z ) );
  transform.setRotation( new AmmoLib.btQuaternion( quaternion.x, quaternion.y, quaternion.z, quaternion.w ) );
  const motionState = new AmmoLib.btDefaultMotionState( transform );
  
  const localInertia = new AmmoLib.btVector3( inertia.x, inertia.y, inertia.z );
  physicsShape.calculateLocalInertia( mass,  localInertia );

  const rbInfo = new AmmoLib.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
  const body = new AmmoLib.btRigidBody( rbInfo );

  if( mass > 0 ) {

    body.setActivationState( DISABLE_DEACTIVATION );

  }
  
  return body;

}

// 生成铰链约束
function createHingeConstraint( rbA, rbB, frameInA, frameInB, useReferenceFrameA ) {
  
  if( !AmmoLib ){

    console.error( "Ammo is not initialized.");
    return null;

  }

  const hinge = new AmmoLib.btHingeConstraint( 
    rbA, rbB, 
    frameInA, frameInB,
    useReferenceFrameA 
  );
  
  return hinge;

}

// 生成6Dof约束
function create6DofConstraint( rbA, rbB, frameInA, frameInB, useReferenceFrameA ) {
  
  if( !AmmoLib ){

    console.error( "Ammo is not initialized.");
    return null;

  }

  const constraint = new AmmoLib.btGeneric6DofConstraint  (
    rbA,
    rbB,
    frameInA,
    frameInB,
    useReferenceFrameA
  );

  return constraint;

}

// 生成滑动约束
function createSliderConstraint( rbA, rbB, frameInA, frameInB, useReferenceFrameA ) {
  
  if( !AmmoLib ){

    console.error( "Ammo is not initialized.");
    return null;

  }

  const constraint = new AmmoLib.btSliderConstraint  (
    rbA,
    rbB,
    frameInA,
    frameInB,
    useReferenceFrameA
  );

  return constraint;

}

// toAmmoType
function toAmmoType( obj ) {

  if( !AmmoLib ){

    console.error( "Ammo is not initialized.");
    return null;

  }

  if( obj.isVector3 ) {
    return new AmmoLib.btVector3( obj.x, obj.y, obj.z );
  }

  if( obj.isQuaternion ) {
    return new AmmoLib.btQuaternion( obj.x, obj.y, obj.z, obj.w );
  }

  return null;

}

// position + quaternion转换为transform
function toTransform( position, quaternion ) {

  if( !AmmoLib ){

    console.error( "Ammo is not initialized.");
    return null;

  }

  const transform = new AmmoLib.btTransform();
  transform.setIdentity();

  if( !isNull(position) ) {
    transform.setOrigin( new AmmoLib.btVector3( position.x, position.y, position.z ) );
  }

  if( !isNull(quaternion) ) {
    transform.setRotation( new AmmoLib.btQuaternion( quaternion.x, quaternion.y, quaternion.z, quaternion.w ) );
  }

  return transform;

}
// 更新transform
function updateTransform( transform, position, quaternion ) {
  
  if( !AmmoLib ){

    console.error( "Ammo is not initialized.");
    return;

  }

  if( !isNull(position) ) {
    transform.setOrigin( new AmmoLib.btVector3( position.x, position.y, position.z ) );
  }

  if( !isNull(quaternion) ) {
    transform.setRotation( new AmmoLib.btQuaternion( quaternion.x, quaternion.y, quaternion.z, quaternion.w ) );
  }

}

// 设置视觉模型与物理模型的指向
function setUserPointer( threeObject, physicsBody ) {

  if( !AmmoLib ){

    console.error( "Ammo is not initialized.");
    return null;

  }

  threeObject.userData.physicsBody = physicsBody;

  const btVecUserData = new AmmoLib.btVector3( 0, 0, 0 );
  btVecUserData.threeObject = threeObject;
  physicsBody.setUserPointer( btVecUserData );

}

function setActorUserPointer ( actor ) {

  if( !AmmoLib ){

    console.error( "Ammo is not initialized.");
    return null;

  }

  actor.threeObject.userData.physicsBody = actor.physicsBody;

  const btVecUserData = new AmmoLib.btVector3( 0, 0, 0 );
  btVecUserData.threeObject = actor.threeObject;
  btVecUserData.actor = actor;
  actor.physicsBody.setUserPointer( btVecUserData );

}

// 从碰撞管理中获取碰撞模型
function getContactObjects( contactManifold ) {

  if( !AmmoLib ){

    console.error( "Ammo is not initialized.");
    return null;

  }

  const rb0 = AmmoLib.castObject( contactManifold.getBody0(), AmmoLib.btRigidBody );
  const rb1 = AmmoLib.castObject( contactManifold.getBody1(), AmmoLib.btRigidBody );

  const actor0 = AmmoLib.castObject( rb0.getUserPointer(), AmmoLib.btVector3 ).actor;
  const actor1 = AmmoLib.castObject( rb1.getUserPointer(), AmmoLib.btVector3 ).actor;
  // console.log( rb0, rb1 )
	return {
    actor0:actor0,
    actor1:actor1,
  }			
}

function getContactPoints( contactManifold ) {

  const points = [];
  // console.log( contactManifold, contactManifold.getNumContacts() );
  for ( let j = 0, jl = contactManifold.getNumContacts(); j < jl; j ++ ) {

    const contactPoint = contactManifold.getContactPoint( j );
    

    if ( contactPoint.getDistance() < 0 ) {

      // const impulse = contactPoint.getAppliedImpulse();

      const pos = contactPoint.get_m_positionWorldOnB();
      // const normal = contactPoint.get_m_normalWorldOnB();

      points.push( new THREE.Vector3( pos.x(), pos.y(), pos.z() ) );

    }

  }

  return points;

}

// 判断对象是否为空
function isNull( obj ){

  return ( obj === null || obj === undefined ) ;

}

// 生成随机材质
function createMaterial( color , transparent=false, wireframe=false) {

  return new THREE.MeshPhongMaterial( { 
    color: color?color:createRandomColor(), 
    transparent: transparent, 
    opacity: 0.5,
    wireframe:wireframe
  } );

}

function createRandomColor() {

  return Math.floor( Math.random() * ( 1 << 24 ) );

}

// 释放资源
function disposeMesh( mesh ) {

  if( isNull( mesh ) ) return;

  for( var i = 0, il = mesh.children; i < il; i++ ) {
    disposeMesh( mesh.children[i] );
  }

  if( mesh.material ) {
    mesh.material.dispose();
  }

  if( mesh.geometry ) {
    mesh.geometry.dispose();
  }

}

export { 
  ACTIVE_TAG, ISLAND_SLEEPING, WANTS_DEACTIVATION, DISABLE_DEACTIVATION, DISABLE_SIMULATION,
  SPI, HPI, DPI,
  AxisX, AxisY, AxisZ, 
  CollisionFlags,

  addAxesHelper,
  createEmptyShape,
  createCompoundShape,
  createConvexHullPhysicsShape, 
  createGImpactCollision,
  createHingeConstraint,
  create6DofConstraint,
  createSliderConstraint,
  createMaterial,
  createRigidBody,
  createTerrainShape,
  toTransform,
  updateTransform,
  toAmmoType,
  getMeshShape,
  getMeshData,   
  isNull,
  setUserPointer,
  setActorUserPointer,
  getContactObjects,
  getContactPoints,

  disposeMesh,
};